/*!
 * IndexedDB 模块
 * @author: __author__
 * @version: __buildVersion__
 * @buildDate：__buildDate__
 * IndexedDB API: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
 * IDBKeyRange API: https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange
 * 
 * 关于store 的创建，删除，创建索引，删除索引功能，只能放到 versionchange 事务中调用
 * createStore: https://developer.mozilla.org/zh-CN/docs/Web/API/IDBDatabase/createObjectStore
 * deleteStore: https://developer.mozilla.org/zh-CN/docs/Web/API/IDBDatabase/deleteObjectStore
 */
export let version: string = '__buildVersion__';

interface I_IDBOpts {
    /**
        ```
        // 参考代码
        onupgradeneeded: (evt) => {

            // 获得 IDBOpenDBRequest 对象
            const db:IDBDatabase = (evt.target as IDBOpenDBRequest).result;

            // 判断store是否存在
            if (!db.objectStoreNames.contains('files')) {
                // 创建store
                let objectStore = db.createObjectStore('files', { keyPath: 'name', autoIncrement: true});
                 // 创建索引
                objectStore.createIndex('type', 'type', { unique: false });
                 // 创建唯一索引
                objectStore.createIndex('id', 'id', { unique: true });
            }

        }
        ```
     */
    onupgradeneeded?: (evt: IDBVersionChangeEvent) => void,
    onblocked?: (evt: Event) => void,

    /**
        ```
        onsuccess(evt) {
            const db: IDBDatabase = (evt.target as IDBOpenDBRequest).result;
        }
        ```
     */
    onsuccess?: (evt: Event) => void,
    onerror?: (evt: Event) => void
};

interface I_IDBIndexResult {

    /**
     * 查询一条数据
     * ```
     * idb.store('files').get('11').then(res=> console.log(res)); // 根据主键值查询一条记录
     * idb.store('files').get(IDBKeyRange.only('11')).then(res=> console.log(res)); // 根据 IDBKeyRange 对象来查询一条记录
     * idb.store('files').get(data => data.id === 5).then(res=> console.log(res)); // 根据函数来查询一条记录
     * ```
     */
    get: (keyOrKeyRangeOrFn: I_KeyOrKeyRangeOrFn) => Promise<any>,

    /**
     * 查询多条数据
     * ```
     * idb.store('files').gets().then(res=> console.log(res)); // 查询全部记录
     * idb.store('files').gets('11').then(res=> console.log(res)); // 根据主键值查询多条记录
     * idb.store('files').gets(IDBKeyRange.only('11')).then(res=> console.log(res)); // 根据 IDBKeyRange 对象来查询多条记录
     * idb.store('files').gets(data => data.id < 5).then(res=> console.log(res)); // 根据函数来查询多条记录
     * 
     * ```
     */
    gets: (keyOrKeyRangeOrFn?: I_KeyOrKeyRangeOrFn, opts?: { count: number }) => Promise<any>,

}

interface I_IDBStoreResult extends I_IDBIndexResult {

    /**
     * 添加数据
     * ```
     * idb.store('files').add({
            id: 0,
            name: `bigcat`,
            type: 'jpg',
            data: '',
        })
     * ```
     */
    add: (data: any, key?: IDBValidKey) => Promise<Event>,

    /**
     * 批量添加数据
     * ```
     * idb.store('files').adds([
            {
                name: `smallcat`,
                type: 'jpg',
                data: '',
            },
            {
                name: `bigcat`,
                type: 'bmp',
                data: '',
            },
        ])
     * ```
     */
    adds: (datas: any[]) => Promise<any>,

    /**
     * 添加数据
     * ```
        // 主键包含在数据中
        idb.store('files').update({
            id: 10
            name: `bigcat`,
            type: 'jpg',
            data: '',
        }).then(res => {
            let key = (res.target as IDBRequest).result; // 修改数据影响到的主键
            console.log('update key:', key);
        });

        // 另外指定主键 10
        idb.store('files').update({
            name: `bigcat`,
            type: 'jpg',
            data: '',
        }, 10);
     * ```
     */
    update: (data: any, key?: IDBValidKey) => Promise<Event>,

    /** 删除数据 */
    delete: (keyOrKeyRangeOrFn: I_KeyOrKeyRangeOrFn) => Promise<any>,

    /** 获得记录数量 */
    count: (keyOrKeyRangeOrFn?: I_KeyOrKeyRangeOrFn) => Promise<number>,

    /** 清空数据 */
    clear: () => Promise<Event>,

    /** 设置索引数据 */
    index: (indexName: string) => I_IDBIndexResult

};

type I_KeyOrKeyRangeOrFn = string | number | IDBKeyRange | ((data: any) => boolean | void);

/**
    参考代码：
    ```
    new IDB('demoDataBase', 1, {
        onupgradeneeded: (evt) => {},
        onsuccess: (evt) => {},
        onerror: (evt) = {},
        onblocked: (evt) => {},
    };
    ```
*/
class IDB {

    static indexedDB = (win => win.indexedDB)(self ?? window); //self.indexedDB || window.indexedDB;
    private databaseName = '';
    private db!: Promise<IDBDatabase>;

    public constructor(databaseName: string, version = 1, opts: I_IDBOpts) {

        if (!IDB.indexedDB) {
            throw 'IndexedDB: Does not support indexedDB';
        }

        // 自动打开数据库
        databaseName && this.openDataBase(databaseName, version, opts);

    }

    // 🆗 打开数据库
    public openDataBase(databaseName: string, version = 1, opts: I_IDBOpts): Promise<IDBDatabase> {

        opts = {
            onupgradeneeded: (_evt) => { },
            onsuccess: (_evt) => { },
            onerror: (_evt) => { },
            onblocked: (_evt) => { },
            ...opts
        };

        this.databaseName = databaseName;

        return this.db = new Promise((resolve, reject) => {

            //IDBOpenDBRequest
            let openDBRequest: IDBOpenDBRequest = IDB.indexedDB.open(databaseName, version);

            openDBRequest.onupgradeneeded = opts.onupgradeneeded!;

            openDBRequest.onblocked = (evt) => {
                opts.onblocked!(evt);
                reject(evt);
            };

            openDBRequest.onsuccess = (evt) => {

                // IDBDatabase
                let db: IDBDatabase = (evt.target as IDBOpenDBRequest).result;

                //避免多窗口时造成数据错误
                db.onversionchange = (_evt) => {
                    db.close();
                    throw 'IndexDB: Page content has expired, please refresh';
                };

                resolve(db);
                opts.onsuccess!(evt);

            };

            openDBRequest.onerror = (evt) => {
                opts.onerror!(evt);
                reject(evt);
            };

        });

    }

    // 🆗 关闭数据库
    public closeDataBase(): Promise<IDBDatabase> {

        return new Promise(async (resolve, _reject) => {
            let db: IDBDatabase = await this.db;
            db.close();
            resolve(db);
        });

    }

    // 🆗 删除数据库
    public deleteDataBase(): Promise<Event> {
        return IDB.deleteDataBase(this.databaseName);
    }

    // 🆗 删除数据库(静态方法)
    static deleteDataBase(databaseName: string): Promise<Event> {
        return new Promise((resolve, reject) => {
            let dbDeleteRequest: IDBOpenDBRequest = IDB.indexedDB.deleteDatabase(databaseName);
            dbDeleteRequest.onsuccess = resolve;
            dbDeleteRequest.onerror = reject;
        });
    }

    // Store 数据操作
    public store(storeName: string, _mode?: 'readonly' | 'readwrite'): I_IDBStoreResult {

        let that = this;
        let cacheData = {
            indexName: ''
        };

        return {

            // 创建数据
            add: (data, key?) => {

                return new Promise(async (resolve, reject) => {

                    let db = await that.db;
                    let tx = db.transaction(storeName, 'readwrite');
                    let store = tx.objectStore(storeName);
                    let request = store.add(data, key);
                    request.onsuccess = resolve;
                    request.onerror = reject;

                });

            },

            // 批量创建数据
            adds: (datas) => {

                return new Promise(async (resolve) => {

                    let db = await that.db;
                    let tx = db.transaction(storeName, 'readwrite');
                    let store = tx.objectStore(storeName);
                    let res: any[] = [];
                    let n = 0;

                    datas.forEach(data => {
                        let request = store.add(data);
                        request.onsuccess = (evt) => {
                            res.push({
                                data,
                                success: evt
                            });

                            n++;
                            if (n === datas.length) {
                                resolve(res);
                            }
                        };
                        request.onerror = (evt) => {
                            res.push({
                                data,
                                error: evt
                            });

                            n++;
                            if (n === datas.length) {
                                resolve(res);
                            }
                        }
                    });

                });

            },

            // 修改数据（必须有主键）
            update(data, key?) {

                return new Promise(async (resolve, reject) => {

                    let db = await that.db;
                    let tx = db.transaction(storeName, 'readwrite');
                    let store = tx.objectStore(storeName);
                    let request = store.put(data, key);
                    request.onsuccess = resolve;
                    request.onerror = reject;

                });

            },

            // 删除数据
            delete(keyOrKeyRangeOrFn) {

                return new Promise(async (resolve, reject) => {

                    let db = await that.db;
                    let tx = db.transaction(storeName, 'readwrite');
                    let store = tx.objectStore(storeName);

                    //按回调函数全数据遍历查询
                    if (typeof keyOrKeyRangeOrFn === 'function') {

                        let request = store.openCursor();
                        let result: any[] = []; //被删除的记录集合

                        request.onsuccess = (evt: Event) => {

                            let cursor: IDBCursorWithValue = (evt.target as IDBRequest).result;

                            if (cursor) {

                                // 查询到记录
                                if (keyOrKeyRangeOrFn(cursor.value) === true) {
                                    console.log(store);
                                    let request = store.delete(cursor.value[store.keyPath as string]);
                                    request.onsuccess = (evt: Event) => {
                                        result.push(evt);
                                    };
                                }

                                cursor.continue();

                            } else {
                                resolve(result);
                            }

                        }

                    } else { //keyOrKeyRangeOrFn === IDBKeyRange || String
                        let request = store.delete(keyOrKeyRangeOrFn);
                        request.onsuccess = resolve;
                        request.onerror = reject;

                    }

                });

            },

            // 清空store
            clear() {

                return new Promise(async (resolve, reject) => {

                    let db = await that.db;
                    let tx = db.transaction(storeName, 'readwrite');
                    let store = tx.objectStore(storeName);
                    let request = store.clear();
                    request.onsuccess = resolve;
                    request.onerror = reject;

                });

            },

            // 查询一条
            get(keyOrKeyRangeOrFn) {

                return new Promise(async (resolve, reject) => {

                    let db = await that.db;
                    let tx = db.transaction(storeName, 'readonly');
                    let store: IDBObjectStore | IDBIndex = tx.objectStore(storeName);

                    if (cacheData.indexName) {
                        store = store.index(cacheData.indexName); // IDBIndex
                    }

                    //按回调函数全数据遍历查询
                    if (typeof keyOrKeyRangeOrFn === 'function') {

                        let request = store.openCursor();

                        request.onsuccess = (evt: Event) => {

                            //IDBCursorWithValue
                            let cursor: IDBCursorWithValue = (evt.target as IDBRequest).result;
                            if (cursor) {

                                // 查询到记录
                                if (keyOrKeyRangeOrFn(cursor.value) === true) {
                                    resolve(cursor.value);
                                    return;
                                }

                                cursor.continue();

                            } else {
                                resolve(null);
                            }

                        }

                    } else { //keyOrKeyRangeOrFn === IDBKeyRange || String

                        //IDBRequest
                        let request = store.get(keyOrKeyRangeOrFn);
                        request.onsuccess = evt => resolve((evt.target as IDBRequest).result);
                        request.onerror = reject;

                    }

                });

            },

            // 查询多条
            gets(keyOrKeyRangeOrFn?, opts?) {

                return new Promise(async (resolve, reject) => {

                    opts = Object.assign({ count: 0 }, opts); // count: 返回多少记录， 0 全部

                    let db = await that.db;
                    let tx = db.transaction(storeName, 'readonly');
                    let store: IDBObjectStore | IDBIndex = tx.objectStore(storeName);

                    if (cacheData.indexName) {
                        store = store.index(cacheData.indexName); // IDBIndex
                    }

                    if (typeof keyOrKeyRangeOrFn === 'function') {

                        let result: any[] = [];
                        let request = store.openCursor();

                        request.onsuccess = evt => {

                            //IDBCursorWithValue
                            let cursor: IDBCursorWithValue = (evt.target as IDBRequest).result;
                            if (cursor) {

                                // 查询到记录
                                if (keyOrKeyRangeOrFn(cursor.value) === true) {
                                    result.push(cursor.value);
                                }

                                // 返回限定的记录数
                                if (opts!.count && result.length === opts!.count) {
                                    resolve(result);
                                    return;
                                }

                                cursor.continue();

                            } else {
                                resolve(result);
                            }

                        }

                    } else {

                        // https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/getAll
                        let request = store.getAll(keyOrKeyRangeOrFn, opts.count);
                        request.onsuccess = (evt: Event) => resolve((evt.target as IDBRequest).result);
                        request.onerror = reject;

                    }

                });

            },

            // 获得记录数
            count(keyOrKeyRangeOrFn?) {

                return new Promise(async (resolve, reject) => {

                    let db = await that.db;
                    let tx = db.transaction(storeName, 'readonly');
                    let store: IDBObjectStore | IDBIndex = tx.objectStore(storeName);

                    if (cacheData.indexName) {
                        store = store.index(cacheData.indexName); // IDBIndex
                    }

                    //按回调函数全数据遍历查询
                    if (typeof keyOrKeyRangeOrFn === 'function') {

                        let request = store.openCursor();
                        let count = 0;
                        request.onsuccess = evt => {

                            //IDBCursorWithValue
                            let cursor: IDBCursorWithValue = (evt.target as IDBRequest).result;

                            if (cursor) {

                                // 查询到记录
                                if (keyOrKeyRangeOrFn(cursor.value) === true) {
                                    count++;
                                }

                                cursor.continue();

                            } else {
                                resolve(count);
                            }

                        }

                    } else { //keyOrKeyRangeOrFn === IDBKeyRange || String

                        let request = store.count(keyOrKeyRangeOrFn);

                        request.onsuccess = evt => {
                            resolve((evt.target as IDBRequest).result);
                        };
                        request.onerror = reject;

                    }

                });

            },

            // 设置索引
            index(indexName) {
                cacheData.indexName = indexName;
                return {
                    get: this.get,
                    gets: this.gets
                };
            }

        };

    }

}

export default IDB;